﻿using System.Diagnostics;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Controls;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using TeamExplorer.Merge.Base;
using TeamExplorer.Merge.Helper;
using Microsoft.TeamFoundation.Controls.WPF;
using System.ComponentModel;
using System.Windows.Data;
using System.Threading;
using TCS = System.Threading.Tasks.TaskCompletionSource<System.Collections.Generic.IEnumerable<TeamExplorer.Merge.MergeCandidates.MergeCandidateViewModel>>;

namespace TeamExplorer.Merge.MergeCandidates
{
    [TeamExplorerPageAttribute(MergeCandidatesPage.PageId)]
    public class MergeCandidatesPage : TeamExplorerBasePage
    {
        public const string PageId = "B1380F8A-D23C-48D6-8BF3-15C914DDE113";
        
        private PropertyObserver<BranchListViewModel> _branchObserver;

        private ObservableCollection<MergeCandidateViewModel> mergeCandidatesCollection;
        private WorkspaceViewModel _workspaceViewModel;
        private BranchListViewModel _branchListViewModel  = null;
        private bool _noResultsAvailable;

        TCS currentTcs;

        private ITeamExplorer teamExplorer;
        private VersionControlServer vcs;
        private TeamFoundationIdentity identity;
        private bool _operationCancelled;
        private int _pendingMergeChangesetId = 0;

        public ICommand GetMergeCandidatesCommand { get; private set; }
        public ICommand CancelCommand { get; private set; }
        public ICommand MergeChangesetCommand { get; private set; }
        public ICommand DiscardCommand { get; private set; }
        
        public MergeCandidatesPage()
        {
            this.Title = "Merge Candidates";
            this.PageContent = new MergeCandidatesPageView();
            View.SetViewModel(this);
            SetupCommands();
        }

        private void SetupCommands()
        {
            GetMergeCandidatesCommand = new DelegateCommand(ExecuteGetMergeCandidates, CanExecuteGetMergeCandidates);
            CancelCommand = new DelegateCommand(ExecuteCancel, CanExecuteCancel);
            MergeChangesetCommand = new DelegateCommand(ExecuteMergeChangeset);
            DiscardCommand = new DelegateCommand(ExecuteDiscardChangeset);
        }

        private bool CanExecuteCancel(object obj)
        {
            return IsBusy;
        }

        private void ExecuteCancel(object obj)
        {
            CancelPendingRequest();
        }

        public override void Initialize(object sender, PageInitializeEventArgs e)
        {
            base.Initialize(sender, e);
            InitializeServices();

            SetCurrentWorkspace();
            if (e.Context == null)
                InitializeBranches();

            ReloadContext(e.Context);
        }

        public override void SaveContext(object sender, PageSaveContextEventArgs e)
        {
            //TODO: create context object to wrap this
            e.Context = new Tuple<BranchListViewModel, ObservableCollection<MergeCandidateViewModel>, int>(_branchListViewModel, MergeCandidatesCollection, _pendingMergeChangesetId);
            base.SaveContext(sender, e);
        }

        public override void Refresh()
        {
            CancelPendingRequest();

            ClearCurrentResults();
            SetCurrentWorkspace();

            if (IsWorkspaceSet)
                GetMergeCandidates();
        }

        private void CancelPendingRequest()
        {
            if (currentTcs == null) return;

            bool cancelled = currentTcs.TrySetCanceled();
            if (cancelled)
            {
                IsBusy = false;
                OperationCancelled = true;
            }

            currentTcs.Task.Dispose();
        }

        private void ExecuteGetMergeCandidates(object obj)
        {
            ClearCurrentResults();
            SetCurrentWorkspace();
            GetMergeCandidates();
        }

        private bool CanExecuteGetMergeCandidates(object obj)
        {
            return !IsBusy && !(String.IsNullOrEmpty(SourceBranch) || String.IsNullOrEmpty(TargetBranch)) && IsWorkspaceSet;
        }

        private void ClearCurrentResults()
        {            
            NoResultsAvailable = false;
            OperationCancelled = false;
            MergeCandidatesCollection = null;
        }
        
        private void InitializeBranches()
        {            
            _branchListViewModel = new BranchListViewModel(vcs);
            SetupPropertyObservers();
            _branchListViewModel.Init();
        }

        private void SetupPropertyObservers()
        {
            _branchObserver = new PropertyObserver<BranchListViewModel>(_branchListViewModel);
            _branchObserver.RegisterHandler(b => b.SourceBranch, a => { RaisePropertyChanged("SourceBranch"); });
            _branchObserver.RegisterHandler(b => b.TargetBranch, a => { RaisePropertyChanged("TargetBranch"); }); 
            _branchObserver.RegisterHandler(b => b.SourceBranches, a => { RaisePropertyChanged("SourceBranches"); RaisePropertyChanged("CanChooseSourceBranch"); });
            _branchObserver.RegisterHandler(b => b.TargetBranches, a => { RaisePropertyChanged("TargetBranches"); RaisePropertyChanged("CanChooseTargetBranch"); });
        }

        private void InitializeServices()
        {
            ITeamFoundationContext context = this.CurrentContext;

            if (context == null || !context.HasCollection || !context.HasTeamProject)
                throw new Exception("TFS Context is missing collection or team project");
            identity = context.TeamProjectCollection.AuthorizedIdentity;
            vcs = context.TeamProjectCollection.GetService<VersionControlServer>();

            teamExplorer = ServiceProvider.GetService(typeof(ITeamExplorer)) as ITeamExplorer;
            if (teamExplorer == null) throw new Exception("Team Explorer service could not be retrieved");
        }

        private void ReloadContext(object context)
        {
            var ctx = context as Tuple<BranchListViewModel, ObservableCollection<MergeCandidateViewModel>, int>;
            if (ctx == null || ctx.Item1 == null) return;

            _branchListViewModel = ctx.Item1;
            SetupPropertyObservers();
            RaisePropertyChanged("");

            if (ctx.Item2 == null) return;

            _pendingMergeChangesetId = ctx.Item3;
            var justMergedCandidate = ctx.Item2.FirstOrDefault(f => f.ChangesetId == _pendingMergeChangesetId);
            if (justMergedCandidate != null)
            {
                justMergedCandidate.JustMerged = true;
            }

            MergeCandidatesCollection = ctx.Item2;
        }

        private void SetCurrentWorkspace()
        {
            _workspaceViewModel = new WorkspaceViewModel(vcs);
            RaisePropertyChanged("CurrentWorkspaceName");            
        }

        public bool IsWorkspaceSet
        {
            get { return !String.IsNullOrEmpty(CurrentWorkspaceName); }
        }
        
        public string CurrentWorkspaceName
        {
            get
            {
                return _workspaceViewModel.CurrentWorkspaceName;
            }
        }

        private void GetMergeCandidates()
        {
            IsBusy = true;
            currentTcs = new TCS();
            Task getCandidatesTask = GetMergeCandidatesInternal(currentTcs);
            
            currentTcs.Task.ContinueWith(SetCandidateCollection, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
        }

        #region Branch Properties

        public ICommand SaveDefaultBranchesCommand
        {
            get
            {
                return _branchListViewModel.SaveDefaultBranchesCommand;
            }
        }

        public ICommand SelectSourceBranchCommand
        {
            get
            {
                return _branchListViewModel.SelectSourceBranchCommand;
            }
        }

        public ICommand SelectTargetBranchCommand
        {
            get
            {
                return _branchListViewModel.SelectTargetBranchCommand;
            }
        }               

        public bool CanChooseSourceBranch
        {
            get { return SourceBranches != null ? SourceBranches.Count() > 1 : false; }
        }

        public bool CanChooseTargetBranch
        {
            get { return TargetBranches != null ? TargetBranches.Count() > 1 : false; }
        }

        public string SourceBranch
        {
            get
            {
                return (_branchListViewModel != null) ? _branchListViewModel.SourceBranch : String.Empty;
            }           
        }

        public string TargetBranch
        {
            get
            {
                return (_branchListViewModel != null) ?_branchListViewModel.TargetBranch : String.Empty;
            }            
        }

        public List<BranchObject> SourceBranches
        {
            get
            {
                return (_branchListViewModel != null) ? _branchListViewModel.SourceBranches : null;
            }
        }

        public List<BranchObject> TargetBranches
        {
            get
            {
                return (_branchListViewModel != null)? _branchListViewModel.TargetBranches : null;
            }
        }

        #endregion

        public bool ShowResults
        {
            get { return MergeCandidatesCollection != null && MergeCandidatesCollection.Count > 0 ; }
        }
        
        public bool NoResultsAvailable
        {
            get { return _noResultsAvailable; }
            set
            {
                _noResultsAvailable = value;
                RaisePropertyChanged("NoResultsAvailable");
            }
        }

        public bool OperationCancelled
        {
            get { return _operationCancelled; }
            set
            {
                _operationCancelled = value;
                RaisePropertyChanged("OperationCancelled");
            }
        }
                
        protected MergeCandidatesPageView View
        {
            get
            {
                return (MergeCandidatesPageView)PageContent;
            }
        }

        public ObservableCollection<MergeCandidateViewModel> MergeCandidatesCollection
        {
            get
            {
                return mergeCandidatesCollection;
            }
            set
            {
                mergeCandidatesCollection = value;                
                RaisePropertyChanged("MergeCandidatesCollection");
                RaisePropertyChanged("ShowResults");
            }
        }
        
        private async Task GetMergeCandidatesInternal(TCS tcs)
        {
            IEnumerable<MergeCandidateViewModel> candidateViewModels = null;
            
            await Task.Run(() =>
            {
                if (SourceBranch == null || TargetBranch == null) return;
                var startTime = DateTime.Now;
                MergeCandidate[] candidates = vcs.GetMergeCandidates(SourceBranch, TargetBranch, RecursionType.Full);
                Debug.WriteLine("Time taken to retrieve merge candidates: " + (DateTime.Now - startTime).ToString());
                candidateViewModels = candidates
                        .Where(c => c.Changeset.OwnerDisplayName == identity.DisplayName)
                        .Select<MergeCandidate, MergeCandidateViewModel>(mc => new MergeCandidateViewModel(mc));
                
                tcs.TrySetResult(candidateViewModels);
            });
        }

        private void SetCandidateCollection(Task<IEnumerable<MergeCandidateViewModel>> arg1, object arg2)
        {
            if (arg1.IsCanceled) { return; } //why do I need to check this?
            MergeCandidatesCollection = arg1.Result != null ? new ObservableCollection<MergeCandidateViewModel>(arg1.Result) : null;
            NoResultsAvailable = MergeCandidatesCollection == null || MergeCandidatesCollection.Count == 0;
            IsBusy = false;
            CommandManager.InvalidateRequerySuggested(); //for Find Changesets button
        }

        private void ExecuteMergeChangeset(object changesetId)
        {
            if (changesetId == null) return;
            _pendingMergeChangesetId = (int)changesetId;

            var details = new MergeCandidateDetails
            {
                ChangesetId = _pendingMergeChangesetId,
                SourceBranch = SourceBranch,
                TargetBranch = TargetBranch,
                CurrentWorkspace = _workspaceViewModel.CurrentWorkspace,
                Action = MergeAction.Merge
            };
            teamExplorer.NavigateToPage(Guid.Parse(MergeCandidatesDetailsPage.PageId), details);
        }

        private void ExecuteDiscardChangeset(object changesetId)
        {
            if (changesetId == null) return;
            _pendingMergeChangesetId = (int)changesetId;
            
            var details = new MergeCandidateDetails
            {
                ChangesetId = _pendingMergeChangesetId,
                SourceBranch = SourceBranch,
                TargetBranch = TargetBranch,
                CurrentWorkspace = _workspaceViewModel.CurrentWorkspace,
                Action = MergeAction.Discard
            };
            teamExplorer.NavigateToPage(Guid.Parse(MergeCandidatesDetailsPage.PageId), details);
        }

        #region Dispose
        
        public override void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool isDisposing)
        {
            if (!isDisposing) return;

            if (_branchObserver != null)
            {
                _branchObserver.UnregisterHandler(b => b.SourceBranch);
                _branchObserver.UnregisterHandler(b => b.TargetBranch);
                _branchObserver.UnregisterHandler(b => b.SourceBranches);
                _branchObserver.UnregisterHandler(b => b.TargetBranches);
            }
        }

        #endregion
    }
}
